#include "CFile.h"

CFile::CFile()
{
    InitMms();
}

CFile::CFile(IN const CFile& File)
{
    InitMms();
    m_hFile = File.m_hFile;
    m_OpenFlags = File.m_OpenFlags;
    SetFilePathName(File.m_lpFileFullName);
}

CFile::CFile(IN HANDLE hFile, IN UINT OpenFlags)
{
    InitMms();
    m_hFile = hFile;
    m_OpenFlags = OpenFlags;
    if (hFile != INVALID_HANDLE_VALUE)
        GetFilePathNameByHandle(hFile);
}

CFile::CFile(IN LPCTSTR lpFileName, IN UINT OpenFlags)
{
    InitMms();
    Open(lpFileName, OpenFlags);
}

CFile::~CFile()
{
    ClearRs();
}

void CFile::InitMms()
{
    m_hFile = INVALID_HANDLE_VALUE;
    m_lpFileFullName = nullptr;
    m_bNeedToClose = FALSE;
    m_OpenFlags = 0;
}

void CFile::ClearRs()
{
    if (m_hFile != INVALID_HANDLE_VALUE && m_bNeedToClose)
    {
        CloseHandle(m_hFile);
        m_hFile = INVALID_HANDLE_VALUE;
        m_bNeedToClose = FALSE;
    }

    if (m_lpFileFullName)
    {
        delete m_lpFileFullName;
        m_lpFileFullName = nullptr;
    }
}

void CFile::SetFilePathName(IN LPCTSTR lpFileName)
{
    if (!lpFileName) return;

    unsigned int nLength = _tcsclen(lpFileName);

    if (m_lpFileFullName) delete m_lpFileFullName;

    m_lpFileFullName = new TCHAR[nLength + 1];
    if (!m_lpFileFullName) return;
    RtlZeroMemory(m_lpFileFullName, sizeof(TCHAR) * (nLength + 1));
    RtlCopyMemory(m_lpFileFullName, lpFileName, nLength * sizeof(TCHAR));
}

void CFile::GetFilePathNameByHandle(IN HANDLE hFile)
{
    TCHAR strPathName[MAX_PATH] = { 0 };
    PTCHAR pTch;

    if (!GetFinalPathNameByHandle(hFile, strPathName, MAX_PATH, VOLUME_NAME_DOS)) return;

    pTch = strPathName;
    while (*pTch != _T('?')) pTch++;
    pTch += 2;

    SetFilePathName(pTch);
}

CFile::operator HANDLE() const
{
    return m_hFile;
}

CFile& CFile::operator=(const CFile& File)
{
    InitMms();
    m_hFile = File.m_hFile;
    SetFilePathName(File.m_lpFileFullName);
    return *this;
}

#define GET_DESIRED_ACCESS(Flags)       (Flags & 0x03)
#define GET_EXCLUSIVE_MODE(Flags)       (Flags & 0x70)
#define GET_CREATE_DISPOSITION(Flags)   (Flags & 0x700)

BOOL CFile::Open(IN LPCTSTR lpFileName, IN UINT OpenFlags)
{
    DWORD dwDesiredAccess = 0;
    DWORD dwShareMode = 0;
    DWORD dwCreationDisposition = 0;
    DWORD dwFlagsAndAttributes = 0;

    switch (GET_DESIRED_ACCESS(OpenFlags))
    {
    case OF::READ:
        dwDesiredAccess = GENERIC_READ;
        break;
    case OF::WRITE:
        dwDesiredAccess = GENERIC_WRITE;
        break;
    case OF::READ_WRITE:
        dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
        break;
    default:
        return FALSE;
    }

    switch (GET_EXCLUSIVE_MODE(OpenFlags))
    {
    case 0:
    case OF::EXCLUSIVE_ALL:
        dwShareMode = 0;
        break;
    case OF::EXCLUSIVE_WRITE:
        dwShareMode = FILE_SHARE_READ;
        break;
    case OF::EXCLUSIVE_READ:
        dwShareMode = FILE_SHARE_WRITE;
        break;
    case OF::EXCLUSIVE_NONE:
        dwShareMode = FILE_SHARE_WRITE | FILE_SHARE_READ;
        break;
    default:
        return FALSE;
    }

    switch (GET_CREATE_DISPOSITION(OpenFlags))
    {
    case OF::CREATE:
        dwCreationDisposition = CREATE_ALWAYS;
        break;
    case 0:
    case OF::OPEN:
        dwCreationDisposition = OPEN_EXISTING;
        break;
    case OF::OPEN_IF:
        dwCreationDisposition = OPEN_ALWAYS;
        break;
    default:
        return FALSE;
    }

    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;

    m_hFile = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, nullptr, dwCreationDisposition, dwFlagsAndAttributes, nullptr);
    if (m_hFile == INVALID_HANDLE_VALUE)
        return FALSE;

    GetFilePathNameByHandle(m_hFile);
    m_OpenFlags = OpenFlags;
    m_bNeedToClose = TRUE;

    return TRUE;
}

void CFile::Close()
{
    ClearRs();
}

BOOL CFile::Rename(IN LPCTSTR lpNewFileName)
{
    BOOL bRet = FALSE;
    TCHAR strFileName[MAX_PATH] = { 0 };
    unsigned int length;
    PTCHAR pTch;

    do
    {
        if (!lpNewFileName || !m_lpFileFullName) break;

        length = _tcslen(m_lpFileFullName);
        RtlCopyMemory(strFileName, m_lpFileFullName, length * sizeof(TCHAR));

        pTch = strFileName + length;
        while (*pTch != _T('\\')) pTch--;
        pTch += 1;

        length = _tcslen(lpNewFileName);
        RtlZeroMemory(pTch, (length + 1) * sizeof(TCHAR));
        RtlCopyMemory(pTch, lpNewFileName, (length + 1) * sizeof(TCHAR));

        if (m_hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(m_hFile);
            m_bNeedToClose = FALSE;
        }

        bRet = ::MoveFile(m_lpFileFullName, strFileName);
        if (!bRet)
        {
            if (m_hFile == INVALID_HANDLE_VALUE) Open(m_lpFileFullName, m_OpenFlags);
            break;
        }

        ClearRs();
        bRet = Open(strFileName, m_OpenFlags);

    } while (FALSE);

    return bRet;
}

BOOL CFile::Delete()
{
    TCHAR strFileName[MAX_PATH] = { 0 };
    unsigned int length;

    if (!m_lpFileFullName) return FALSE;

    length = _tcslen(m_lpFileFullName);
    RtlCopyMemory(strFileName, m_lpFileFullName, length * sizeof(TCHAR));

    ClearRs();

    return ::DeleteFile(strFileName);
}

BOOL CFile::MoveTo(IN LPCTSTR lpNewFilePathName)
{
    BOOL bRet = FALSE;

    do
    {
        if (!lpNewFilePathName || !m_lpFileFullName) break;

        if (m_hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(m_hFile);
            m_bNeedToClose = FALSE;
        }

        bRet = ::MoveFile(m_lpFileFullName, lpNewFilePathName);
        if (!bRet)
        {
            if (m_hFile == INVALID_HANDLE_VALUE) Open(m_lpFileFullName, m_OpenFlags);
            break;
        }

        ClearRs();
        bRet = Open(lpNewFilePathName, m_OpenFlags);

    } while (FALSE);

    return bRet;
}

BOOL CFile::CopyTo(IN LPCTSTR lpNewFilePathName, IN BOOL bOpenNew, IN BOOL bOverWrite)
{
    BOOL bRet = FALSE;

    do
    {
        if (!lpNewFilePathName || !m_lpFileFullName) break;

        if (m_hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(m_hFile);
            m_bNeedToClose = FALSE;
        }

        bRet = ::CopyFile(m_lpFileFullName, lpNewFilePathName, !bOverWrite);
        if (!bRet)
        {
            if (m_hFile == INVALID_HANDLE_VALUE) Open(m_lpFileFullName, m_OpenFlags);
            break;
        }

        if (bOpenNew)
        {
            ClearRs();
            bRet = Open(lpNewFilePathName, m_OpenFlags);
        }
        else
        {
            bRet = Open(m_lpFileFullName, m_OpenFlags);
        }

    } while (FALSE);

    return bRet;
}

BOOL CFile::Read(IN PVOID lpBuff, IN DWORD dwNumberOfBytesToRead, OUT LPDWORD lpNumberOfBytesRead)
{
    return ReadFile(m_hFile, lpBuff, dwNumberOfBytesToRead, lpNumberOfBytesRead, nullptr);
}

BOOL CFile::Write(IN PVOID lpBuff, IN DWORD dwNumberOfBytesToWrite, OUT LPDWORD lpNumberOfBytesWritten, IN BOOL bTruncate)
{
    if (bTruncate)
        Truncate();

    return WriteFile(m_hFile, lpBuff, dwNumberOfBytesToWrite, lpNumberOfBytesWritten, nullptr);
}

BOOL CFile::Append(IN PVOID lpBuff, IN DWORD dwNumberOfBytesToWrite, OUT LPDWORD lpNumberOfBytesWritten)
{
    SeekToEnd();
    return WriteFile(m_hFile, lpBuff, dwNumberOfBytesToWrite, lpNumberOfBytesWritten, nullptr);
}

BOOL CFile::Flush()
{
    return FlushFileBuffers(m_hFile);
}

ULONGLONG CFile::Seek(IN LONGLONG MoveOffset, IN DWORD MoveFrom)
{
    LARGE_INTEGER Offset = { 0 };
    Offset.QuadPart = MoveOffset;
    return SetFilePointer(m_hFile, Offset.LowPart, &Offset.HighPart, MoveFrom);
}

ULONGLONG CFile::SeekToEnd()
{
    return Seek(0, SP::END);
}

void CFile::SeekToBegin()
{
    Seek(0, SP::BEGIN);
}

void CFile::Truncate()
{
    SeekToBegin();
    SetEndOfFile(m_hFile);
}

void CFile::SetFileSize(IN ULONGLONG Size)
{
    Seek(Size, SP::BEGIN);
    SetEndOfFile(m_hFile);
}

BOOL CFile::SetHide(IN BOOL bHide)
{
    BOOL bRet = FALSE;
    FILE_BASIC_INFO FileBaseInfo = { 0 };

    do
    {
        if (m_hFile == INVALID_HANDLE_VALUE) break;

        bRet = GetFileInformationByHandleEx(m_hFile, 
            FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &FileBaseInfo, sizeof(FILE_BASIC_INFO));
        if (!bRet) break;

        if (bHide)
            FileBaseInfo.FileAttributes |= FILE_ATTRIBUTE_HIDDEN;
        else
            FileBaseInfo.FileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;

        bRet = SetFileInformationByHandle(m_hFile, 
            FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &FileBaseInfo, sizeof(FILE_BASIC_INFO));
        if (!bRet) break;

        bRet = TRUE;

    } while (FALSE);

    return bRet;
}

BOOL CFile::SetReadOnly(IN BOOL bReadOnly)
{
    BOOL bRet = FALSE;
    FILE_BASIC_INFO FileBaseInfo = { 0 };

    do
    {
        if (m_hFile == INVALID_HANDLE_VALUE) break;

        bRet = GetFileInformationByHandleEx(m_hFile, 
            FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &FileBaseInfo, sizeof(FILE_BASIC_INFO));
        if (!bRet) break;

        if (bReadOnly)
            FileBaseInfo.FileAttributes |= FILE_ATTRIBUTE_READONLY;
        else
            FileBaseInfo.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;

        bRet = SetFileInformationByHandle(m_hFile, 
            FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &FileBaseInfo, sizeof(FILE_BASIC_INFO));
        if (!bRet) break;

        bRet = TRUE;

    } while (FALSE);

    return bRet;
}

BOOL CFile::EncryptByRc4(IN const unsigned char* user16key, IN LPCTSTR lpEncryptFilePath, IN BOOL bDelete)
{
    BOOL bRet = FALSE;
    unsigned int nFileSize = (unsigned int)GetFileSize();
    unsigned int nFileAlignSize = 0;
    unsigned char* lpBuff = nullptr;
    unsigned char* lpCipherText = nullptr;
    RC4_KEY rc4Key;
    unsigned int nGroup;

    do
    {
        if (!user16key || !nFileSize) break;

        nFileAlignSize = ROUND_TO_SIZE(nFileSize, 16);
        if (nFileSize != nFileAlignSize)
        {
            nFileSize = nFileAlignSize;
            SetFileSize(nFileSize);
        }

        lpBuff = new unsigned char[nFileSize];
        if (!lpBuff) break;
        RtlZeroMemory(lpBuff, nFileSize);

        lpCipherText = new unsigned char[nFileSize];
        if (!lpCipherText) break;
        RtlZeroMemory(lpCipherText, nFileSize);

        SeekToBegin();
        if (!Read((LPVOID)lpBuff, nFileSize, nullptr)) break;

        Rc4SetKey(&rc4Key, strlen((char*)user16key), user16key);

        nGroup = nFileSize / 16;

        for (unsigned int i = 0; i < nGroup; ++i)
        {
            Rc4Encrypt(&rc4Key, 16, lpBuff + (i * 16), lpCipherText + (i * 16));
        }

        CFile File(lpEncryptFilePath, CFile::OF::READ_WRITE | CFile::OF::OPEN_IF);
        File.Write(lpCipherText, nFileSize, nullptr);
        File.Close();

        bRet = TRUE;

    } while (FALSE);

    if (lpBuff)
        delete[] lpBuff;

    if (lpCipherText)
        delete[] lpCipherText;

    return bRet;
}

BOOL CFile::DecryptByRc4(IN const unsigned char* user16key, IN LPCTSTR lpDecryptFilePath, IN BOOL bDelete)
{
    BOOL bRet = FALSE;
    unsigned int nFileSize = (unsigned int)GetFileSize();
    unsigned int nFileAlignSize = 0;
    unsigned char* lpBuff = nullptr;
    unsigned char* lpPlainText = nullptr;
    RC4_KEY rc4Key;
    unsigned int nGroup;

    do
    {
        if (!user16key || !nFileSize) break;

        nFileAlignSize = ROUND_TO_SIZE(nFileSize, 16);
        if (nFileSize != nFileAlignSize)
        {
            nFileSize = nFileAlignSize;
            SetFileSize(nFileSize);
        }

        lpBuff = new unsigned char[nFileSize];
        if (!lpBuff) break;
        RtlZeroMemory(lpBuff, nFileSize);

        lpPlainText = new unsigned char[nFileSize];
        if (!lpPlainText) break;
        RtlZeroMemory(lpPlainText, nFileSize);

        SeekToBegin();
        if (!Read((LPVOID)lpBuff, nFileSize, nullptr)) break;

        nGroup = nFileSize / 16;
        Rc4SetKey(&rc4Key, strlen((char*)user16key), user16key);
        for (unsigned int i = 0; i < nGroup; ++i)
        {
            Rc4Encrypt(&rc4Key, 16, lpBuff + (i * 16), lpPlainText + (i * 16));
        }

        CFile File(lpDecryptFilePath, CFile::OF::READ_WRITE | CFile::OF::OPEN_IF);
        File.Write(lpPlainText, nFileSize, nullptr);
        File.Close();

        bRet = TRUE;

    } while (FALSE);

    if (lpBuff)
        delete[] lpBuff;

    if (lpPlainText)
        delete[] lpPlainText;

    return bRet;
}

BOOL CFile::EncryptByAes(IN const unsigned char* user128key, IN LPCTSTR lpEncryptFilePath, IN BOOL bDelete)
{
    BOOL bRet = FALSE;
    unsigned int nFileSize = (unsigned int)GetFileSize();
    unsigned int nFileAlignSize = 0;
    unsigned char* lpBuff = nullptr;
    unsigned char* lpCipherText = nullptr;
    AES_KEY aesKey;
    unsigned char strIvec[AES_BLOCK_SIZE] = { 0 };

    do
    {
        if (!user128key || !nFileSize) break;

        nFileAlignSize = ROUND_TO_SIZE(nFileSize, 128);
        if (nFileSize != nFileAlignSize)
        {
            nFileSize = nFileAlignSize;
            SetFileSize(nFileSize);
        }

        lpBuff = new unsigned char[nFileSize];
        if (!lpBuff) break;
        RtlZeroMemory(lpBuff, nFileSize);

        lpCipherText = new unsigned char[nFileSize];
        if (!lpCipherText) break;
        RtlZeroMemory(lpCipherText, nFileSize);

        SeekToBegin();
        if (!Read((LPVOID)lpBuff, nFileSize, nullptr)) break;

        AesSetEncryptKey(user128key, 128, &aesKey);
        AesCbcEncrypt(lpBuff, lpCipherText, nFileSize, &aesKey, strIvec, AES_ENCRYPT);

        CFile File(lpEncryptFilePath, CFile::OF::READ_WRITE | CFile::OF::OPEN_IF);
        File.Write(lpCipherText, nFileSize, nullptr);
        File.Close();

        bRet = TRUE;

    } while (FALSE);

    if (lpBuff)
        delete[] lpBuff;

    if (lpCipherText)
        delete[] lpCipherText;

    return bRet;
}

BOOL CFile::DecryptByAes(IN const unsigned char* user128key, IN LPCTSTR lpEncryptFilePath, IN BOOL bDelete)
{
    BOOL bRet = FALSE;
    unsigned int nFileSize = (unsigned int)GetFileSize();
    unsigned int nFileAlignSize = 0;
    unsigned char* lpBuff = nullptr;
    unsigned char* lpPlainText = nullptr;
    AES_KEY aesKey;
    unsigned char strIvec[AES_BLOCK_SIZE] = { 0 };

    do
    {
        if (!user128key || !nFileSize) break;

        nFileAlignSize = ROUND_TO_SIZE(nFileSize, 128);
        if (nFileSize != nFileAlignSize)
        {
            nFileSize = nFileAlignSize;
            SetFileSize(nFileSize);
        }

        lpBuff = new unsigned char[nFileSize];
        if (!lpBuff) break;
        RtlZeroMemory(lpBuff, nFileSize);

        lpPlainText = new unsigned char[nFileSize];
        if (!lpPlainText) break;
        RtlZeroMemory(lpPlainText, nFileSize);

        SeekToBegin();
        if (!Read((LPVOID)lpBuff, nFileSize, nullptr)) break;

        AesSetDecryptKey(user128key, 128, &aesKey);
        AesCbcEncrypt(lpBuff, lpPlainText, nFileSize, &aesKey, strIvec, AES_DECRYPT);

        CFile File(lpEncryptFilePath, CFile::OF::READ_WRITE | CFile::OF::OPEN_IF);
        File.Write(lpPlainText, nFileSize, nullptr);
        File.Close();

        bRet = TRUE;

    } while (FALSE);

    if (lpBuff)
        delete[] lpBuff;

    if (lpPlainText)
        delete[] lpPlainText;

    return bRet;
}

ULONGLONG CFile::GetFileSize()
{
    LARGE_INTEGER FileSize = { 0 };
    if (!GetFileSizeEx(m_hFile, &FileSize)) return 0;
    return FileSize.QuadPart;
}

LPCTSTR CFile::GetFileFullName()
{
    return m_lpFileFullName;
}

LPCTSTR CFile::GetFileName()
{
    unsigned int length;
    PTCHAR pTch;

    if (!m_lpFileFullName) return nullptr;

    length = _tcslen(m_lpFileFullName);
    pTch = m_lpFileFullName + length;
    while (*pTch != _T('\\')) pTch--;
    pTch += 1;

    return pTch;
}

BOOL CFile::GetFileTime(OUT PSYSTEMTIME lpCreateTime, OUT PSYSTEMTIME lpLastAccessTime, OUT PSYSTEMTIME lpLastWriteTime, OUT PSYSTEMTIME lpLastChangeTime)
{
    BOOL bRet = FALSE;
    FILE_BASIC_INFO FileBaseInfo = { 0 };

    do
    {
        if (m_hFile == INVALID_HANDLE_VALUE) break;

        bRet = GetFileInformationByHandleEx(m_hFile, 
            FILE_INFO_BY_HANDLE_CLASS::FileBasicInfo, &FileBaseInfo, sizeof(FILE_BASIC_INFO));
        if (!bRet) break;

        if (lpCreateTime)
            FileTimeToSystemTime((FILETIME*)&FileBaseInfo.CreationTime, lpCreateTime);

        if (lpLastAccessTime)
            FileTimeToSystemTime((FILETIME*)&FileBaseInfo.LastAccessTime, lpLastAccessTime);

        if (lpLastWriteTime)
            FileTimeToSystemTime((FILETIME*)&FileBaseInfo.LastWriteTime, lpLastWriteTime);

        if (lpLastChangeTime)
            FileTimeToSystemTime((FILETIME*)&FileBaseInfo.ChangeTime, lpLastChangeTime);

        bRet = TRUE;

    } while (FALSE);

    return bRet;
}
